package com.rs.net.encoders;

import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;

import com.rs.Settings;
import com.rs.game.DynamicRegion;
import com.rs.game.Entity;
import com.rs.game.Graphics;
import com.rs.game.Region;
import com.rs.game.World;
import com.rs.game.WorldObject;
import com.rs.game.WorldTile;
import com.rs.game.item.FloorItem;
import com.rs.game.item.Item;
import com.rs.game.item.ItemsContainer;
import com.rs.game.npc.NPC;
import com.rs.game.player.Player;
import com.rs.io.OutputStream;
import com.rs.net.Session;
import com.rs.utils.Logger;
import com.rs.utils.MapContainersXteas;
import com.rs.utils.Utils;

public class WorldPacketsEncoder extends Encoder {

	private Player player;
	private short interPacketsCount;
	
	
	public WorldPacketsEncoder(Session session, Player player) {
		super(session);
		this.player = player;
	}
	
	public Player getPlayer() {
		return player;
	}
	
	public short getInterPacketsCount() {
		return interPacketsCount;
	}
	
	public void sendHideIComponent(int interfaceId, int componentId, boolean hidden) {
		OutputStream stream = new OutputStream(9);
		stream.writePacket(3);
		stream.writeShort128(interPacketsCount++);
		stream.write128Byte(hidden ? 1 : 0);
		stream.writeIntV2(interfaceId << 16 | componentId);
		session.write(stream);
	}
	public void sendRemoveGroundItem(FloorItem item) {
		sendWorldTile(item.getTile());
		OutputStream stream = new OutputStream(4);
		stream.writePacket(221);
		stream.write128Byte(0); //hash for make right spawn coord, used for big regions
		stream.writeShort(item.getId());
		session.write(stream);
	}
	
	public void sendGroundItem(FloorItem item) {
		sendWorldTile(item.getTile());
		OutputStream stream = new OutputStream(6);
		stream.writePacket(22);
		stream.writeShortLE128(item.getAmount());
		stream.writeShort(item.getId());
		stream.writeByteC(0);//hash for make right spawn coord, used for big regions
		session.write(stream);
	}
	
	
	public void sendProjectile(Entity receiver, WorldTile startTile, WorldTile endTile, int gfxId, int startHeight, int endHeight, int speed, int delay,  int curve, int startDistanceOffset, int creatorSize) {
		sendWorldTile(startTile);
		OutputStream stream = new OutputStream();
		stream.writePacket(223);
		int x = startTile.getX()-startTile.getRegionX() << 3;
		int y = startTile.getY()-startTile.getRegionY() << 3;
		stream.writeByte((x & 0x7) << 3 | y & 0x7);
		stream.writeByte(endTile.getX()-startTile.getX());
		stream.writeByte(endTile.getY()-startTile.getY());
		stream.writeShort(receiver == null ? 0 : (receiver instanceof Player ? -(receiver.getIndex()+1) : receiver.getIndex()+1));
		stream.writeShort(gfxId);
		stream.writeByte(startHeight);
		stream.writeByte(endHeight);
		stream.writeShort(delay);
		int duration = (Utils.getDistance(startTile.getX(), startTile.getY(), endTile.getX(), endTile.getY()) * 30 / ((speed / 10) < 1 ? 1 : (speed / 10))) + delay;
		stream.writeShort(duration);
		stream.writeByte(curve);
		stream.writeByte(creatorSize * 64 + startDistanceOffset * 64);
		session.write(stream);
	}
	
	public void sendUnlockIComponentOptionSlots(int interfaceId, int componentId, int fromSlot, int toSlot, int... optionsSlots) {
		int settingsHash = 0;
		for(int slot : optionsSlots)
			settingsHash |= 2 << slot;
		sendIComponentSettings(interfaceId, componentId, fromSlot, toSlot, settingsHash);
	}
	
	public void sendIComponentSettings(int interfaceId, int componentId, int fromSlot, int toSlot, int settingsHash) {
		OutputStream stream = new OutputStream(15);
		stream.writePacket(113);
		stream.writeInt(settingsHash);
		stream.writeShortLE(toSlot);
		stream.writeShortLE(fromSlot);
		stream.writeShortLE128(interPacketsCount++);
		stream.writeIntLE(interfaceId << 16 | componentId);
		session.write(stream);
	}
	
	public void sendRunScript(int scriptId, Object... params) {
		OutputStream stream = new OutputStream();
		stream.writePacketVarShort(70);
		stream.writeShort(interPacketsCount++);
		 String parameterTypes = "";
		 if(params != null) {
			 for(int count = params.length-1; count >= 0; count--) {
				 if(params[count] instanceof String)
					 parameterTypes += "s"; //string
				 else
					 parameterTypes += "i"; //integer
			 }
		 }
		 stream.writeString(parameterTypes);
		 if(params != null) {
			 int index = 0;
			 for (int count = parameterTypes.length() - 1;count >= 0;count--) {
				 if (parameterTypes.charAt(count) == 's') 
					 stream.writeString((String) params[index++]);
				 else
					 stream.writeInt((Integer) params[index++]);
			 }
		 }
		stream.writeInt(scriptId);
		stream.endPacketVarShort();
		session.write(stream);
	}
	
	public void sendButtonConfig(int id, int value) {
		if(value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
			OutputStream stream = new OutputStream(9);
			stream.writePacket(21);
			stream.writeShortLE128(id);
			stream.writeIntV1(value);
			stream.writeShort(interPacketsCount++);
			session.write(stream);
		}else {
			OutputStream stream = new OutputStream(6);
			stream.writePacket(164);
			stream.writeShortLE128(interPacketsCount++);
			stream.writeByte(value);
			stream.writeShortLE(id);
			session.write(stream);
		}
	}
	
	public void sendConfig(int id, int value) {
		if(value < Byte.MIN_VALUE || value > Byte.MAX_VALUE)
			sendConfig2(id, value);
		else
			sendConfig1(id, value);
	}
	public void sendConfig1(int id, int value) {
		OutputStream stream = new OutputStream(4);
		stream.writePacket(186);
		stream.write128Byte(value);
		stream.writeShortLE128(id);
		session.write(stream);
	}
	
	public void sendConfig2(int id, int value) {
		OutputStream stream = new OutputStream(7);
		stream.writePacket(151);
		stream.writeIntLE(value);
		stream.writeShort(id);
		session.write(stream);
	}
	
	public void sendRunEnergy() {
		OutputStream stream = new OutputStream(2);
		stream.writePacket(63);
		stream.writeByte(player.getRunEnergy());
		session.write(stream);
	}
	
	public void sendIComponentText(int interfaceId, int componentId, String text) {
		OutputStream stream = new OutputStream();
		stream.writePacketVarShort(106);
		stream.writeShortLE128(interPacketsCount++);
		stream.writeString(text);
		stream.writeIntV2(interfaceId << 16 | componentId);
		stream.endPacketVarShort();
		session.write(stream);
	}
	
    public void sendIComponentAnimation(int emoteId, int interfaceId, int componentId) {
    	OutputStream stream = new OutputStream(9);
    	stream.writePacket(61);
    	stream.writeIntV2(interfaceId << 16 | componentId);
    	stream.writeShortLE(emoteId);
    	stream.writeShortLE128(interPacketsCount++);
        session.write(stream);
    }
	
    public void sendItemOnIComponent(int interfaceid, int componentId, int id, int amount) {
    	OutputStream stream = new OutputStream(13);
    	stream.writePacket(145);
    	stream.writeInt(interfaceid << 16 | componentId);
    	stream.writeIntV1(amount);
    	stream.writeShortLE128(interPacketsCount++);
    	stream.writeShortLE128(id);
    	session.write(stream);
    }
    
	public void sendEntityOnIComponent(boolean isPlayer, int entityId, int interfaceId, int componentId) {
		if(isPlayer)
			sendPlayerOnIComponent(interfaceId, componentId);
		else 
			sendNPCOnIComponent(interfaceId, componentId, entityId);
	}
	
	public void sendWorldTile(WorldTile tile) {
		OutputStream stream = new OutputStream(3);
		stream.writePacket(207);
		stream.write128Byte(tile.getLocalY(player.getLastLoadedMapRegionTile(), player.getMapSize()));
		stream.writeByte(tile.getLocalX(player.getLastLoadedMapRegionTile(), player.getMapSize()));
		session.write(stream);
	}

	public void sendSpawnedObject(WorldObject object) {
		sendWorldTile(object);
		OutputStream stream = new OutputStream(5);
		stream.writePacket(115);
		stream.writeByteC(0); //the hash for coords, useless
		stream.writeShortLE128(object.getId());
		stream.writeByteC((object.getType() << 2) + (object.getRotation() & 0x3));
		session.write(stream);
	}
	public void sendPlayerOnIComponent(int interfaceId, int componentId) {
		OutputStream stream = new OutputStream(7);
        stream.writePacket(219);
        stream.writeShort128(interPacketsCount++);
        stream.writeIntV1(interfaceId << 16 | componentId);
        session.write(stream);
   }

	
	public void sendNPCOnIComponent(int interfaceId, int componentId, int npcId) {
		OutputStream stream = new OutputStream(10);
		stream.writePacket(158);
		stream.writeShortLE128(npcId);
		stream.writeShort(interPacketsCount++);
		stream.writeIntV2(interfaceId << 16 | componentId);
		session.write(stream);
	}
	
	public void sendRandomOnIComponent(int interfaceId, int componentId, int id) {
		OutputStream stream = new OutputStream();
		stream.writePacket(235);
		stream.writeShort(id);
		stream.writeIntV1(interfaceId << 16 | componentId);
		stream.writeShort(interPacketsCount++);
		session.write(stream);
	}
	
	public void sendFaceOnIComponent(int interfaceId, int componentId, int look1, int look2, int look3) {
		OutputStream stream = new OutputStream();
		stream.writePacket(192);
		stream.writeIntV2(interfaceId << 16 | componentId);
		stream.writeShortLE128(interPacketsCount++);
		stream.writeShortLE128(look1);
		stream.writeShortLE128(look2); 
		stream.writeShort128(look2); 
		session.write(stream);
	}
	
	/*
	 * dynamic map region
	 */
	public void sendDynamicMapRegion() {
		OutputStream stream = new OutputStream();
		stream.writePacketVarShort(146);
		stream.write128Byte(player.isForceNextMapLoadRefresh() ? 1 : 0);  //force refresh
		int regionX = player.getRegionX();
		int regionY = player.getRegionY();
		stream.writeShort(regionY);
		stream.writeByte128(player.getMapSize());
		stream.writeShortLE128(player.getLocalX(player, player.getMapSize()));
		stream.writeShort128(player.getLocalY(player, player.getMapSize()));
		stream.writeByteC(player.getPlane());
		stream.writeShort(regionX);
		stream.initBitAccess();
		int mapHash = Settings.MAP_SIZES[player.getMapSize()] >> 4;
		int[] realRegionIds = new int[4 * mapHash * mapHash];
		int realRegionIdsCount = 0;
		for(int plane = 0; plane < 4; plane++) {
			for (int thisRegionX = (regionX - mapHash) ; thisRegionX <= ((regionX + mapHash) ); thisRegionX++) { //real x calcs
				for (int thisRegionY = (regionY - mapHash) ; thisRegionY <= ((regionY + mapHash)); thisRegionY++) { //real y calcs
					int regionId = (((thisRegionX / 8) << 8) + (thisRegionY / 8));
					Region region = World.getRegion(regionId);
					int realRegionX;
					int realRegionY;
					int realPlane;
					int rotation;
					if(region instanceof DynamicRegion) { //generated map
						DynamicRegion dynamicRegion = (DynamicRegion) region;
						int[] regionCoords = dynamicRegion.getRegionCoords()[plane][thisRegionX - ((thisRegionX/8) * 8)][thisRegionY - ((thisRegionY/8) * 8)];
						realRegionX = regionCoords[0];
						realRegionY = regionCoords[1];
						realPlane = regionCoords[2];
						rotation = regionCoords[3];
					}else { //real map
						//base region + difference * 8 so gets real region coords
						realRegionX = thisRegionX;
						realRegionY = thisRegionY;
						realPlane = plane;
						rotation = 0;//no rotation
					}
					//invalid region, not built region
					if(realRegionX == 0 || realRegionY == 0)
						stream.writeBits(1, 0);
					else {
						stream.writeBits(1, 1);
						stream.writeBits(26,(rotation << 1) | (realPlane << 24) | (realRegionX << 14) | (realRegionY << 3));
						int realRegionId = (((realRegionX / 8) << 8) + (realRegionY / 8));
						boolean found = false;
						for(int index = 0; index < realRegionIdsCount; index++)
							if(realRegionIds[index] == realRegionId) {
								found = true;
								break;
							}
						if(!found)
							realRegionIds[realRegionIdsCount++] = realRegionId;
					}
					
				}
			}
		}
		stream.finishBitAccess();
		for(int index = 0; index < realRegionIdsCount; index++) {
			int[] xteas = MapContainersXteas.getMapContainerXteas(realRegionIds[index]);
			if(xteas == null)
				xteas = new int[4];
			for(int keyIndex = 0; keyIndex < 4; keyIndex++)
				stream.writeInt(xteas[keyIndex]);
		}
		stream.endPacketVarShort();
		session.write(stream);
	}
	
	/*
	 * normal map region
	 */
	public void sendMapRegion() {
		OutputStream stream = new OutputStream();
		stream.writePacketVarShort(76);
		stream.writeShortLE128(player.getRegionX());
		stream.writeShort(player.getLocalY(player.getLastLoadedMapRegionTile(), player.getMapSize()));
		stream.writeByte(player.getMapSize());
		stream.writeByte128(player.isForceNextMapLoadRefresh() ? 1 : 0); //force refresh
		for(int regionId : player.getMapRegionsIds()) {
			int[] xteas = MapContainersXteas.getMapContainerXteas(regionId);
			if(xteas == null)
				xteas = new int[4];
			for(int index = 0; index < 4; index++)
				stream.writeInt(xteas[index]);
		}
		stream.writeByte128(player.getPlane());
		stream.writeShort(player.getLocalX(player.getLastLoadedMapRegionTile(), player.getMapSize()));
		stream.writeShort(player.getRegionY());
		stream.endPacketVarShort();
		session.write(stream);
	}
	
	/*
	 * sets the pane interface
	 */
	public void sendWindowsPane(int id, int type) {
		player.getInterfaceManager().setWindowsPane(id);
		OutputStream stream = new OutputStream(6);
		stream.writePacket(50);
		stream.writeShort128(id);
		stream.writeShort(interPacketsCount++);
		stream.write128Byte(type);
		session.write(stream);
	}
	
	public void sendPlayerOption(String option, int slot, boolean top) {
		OutputStream stream = new OutputStream();
		stream.writePacketVarByte(73);
		stream.writeShortLE128(65535);
		stream.writeString(option);
		stream.writeByteC(top ? 1 : 0);
		stream.write128Byte(slot);
		stream.endPacketVarByte();
		session.write(stream);
	}
	
	/*
	 * sends local players update
	 */
	public void sendLocalPlayersUpdate() {
		session.write(player.getLocalPlayerUpdate().createPacketAndProcess());
	}
	/*
	 * sends local npcs update
	 */
	public void sendLocalNPCsUpdate() {
		session.write(player.getLocalNPCUpdate().createPacketAndProcess());
	}
	
	public void sendGraphics(Graphics graphics, Object target) {
		OutputStream stream = new OutputStream(11);
		stream.writePacket(109);
		stream.writeShortLE128(graphics.getHeight());
		int hash = 0;
		if(target instanceof WorldTile) {
			WorldTile tile = (WorldTile) target;
			hash = tile.getPlane() << 28 | tile.getX() << 14 | tile.getY() & 0x3fff| 1 << 30;
		}else if (target instanceof Player) {
			Player p = (Player) target;
			hash = p.getIndex() & 0xffff | 1 << 28;
		}else{
			NPC n = (NPC) target;
			hash = n.getIndex() & 0xffff;
		}
		stream.writeIntLE(hash);
		stream.writeShort128(graphics.getId());
		stream.writeShortLE128(graphics.getSpeed());
		session.write(stream);
	}
	
	public void sendDelayedGraphics(Graphics graphics, int delay, WorldTile tile) {
		
	}

	public void closeInterface(int windowComponentId) {
		OutputStream stream = new OutputStream(7);
		stream.writePacket(203);
		stream.writeShort(interPacketsCount++);
		stream.writeInt(player.getInterfaceManager().getTabWindow(windowComponentId) << 16 | windowComponentId);
		session.write(stream);
		player.getInterfaceManager().removeTab(windowComponentId);
	}
	
	public void sendInterface(boolean cliped, int windowId, int windowComponentId, int interfaceId) {
		//currently fixes the inter engine.. not ready for same component ids(tabs), different inters
		if(!(windowId == 752 && windowComponentId == 8 && interfaceId == 137)) { //if chatbox 
		if (player.getInterfaceManager().containsInterface(windowComponentId, interfaceId))
			closeInterface(windowComponentId);
		if (!player.getInterfaceManager().addInterface(windowId, windowComponentId, interfaceId)) {
			Logger.log(this, "Error adding interface: " + windowId + " , "	+ windowComponentId + " , " + interfaceId);
			return;
		}
		}
		OutputStream stream = new OutputStream(10);
		stream.writePacket(56);
		stream.writeShort(interfaceId);
		stream.write128Byte(cliped ? 1 : 0);
		stream.writeShort(interPacketsCount++);
		stream.writeIntLE(windowId << 16 | windowComponentId);
		session.write(stream);
	}
	
	public void sendSystemUpdate(int delay) {
		OutputStream stream = new OutputStream(3);
		stream.writePacket(231);
		stream.writeShortLE((int) ((delay + 3) * 1.6));
		session.write(stream);
  	}
	
	public void sendUpdateItems(int interfaceId, int componentId, int key, ItemsContainer<Item> items, int... slots) {
		OutputStream stream = new OutputStream();
		stream.writePacketVarShort(58);
		stream.writeInt(interfaceId << 16 | componentId);
		stream.writeShort(key);
		for(int slotId : slots) {
			if(slotId >= items.getSize())
				continue;
			stream.writeSmart(slotId);
			int id = -1;
			int amount = 0;
			Item item = items.get(slotId);
			if (item != null) {
				id = item.getId();
				amount = item.getAmount();
			}
			stream.writeShort(id+1);
			if(id != -1) {
				stream.writeByte(amount >= 255 ? 255 : amount);
				if(amount >= 255)
					stream.writeInt(amount);
			}
		}
		stream.endPacketVarShort();
		session.write(stream);
	}
	
	public void sendItems(int interfaceId, int componentId, int key, ItemsContainer<Item> items) {
		OutputStream stream = new OutputStream();
		stream.writePacketVarShort(120);
		stream.writeInt(interfaceId << 16 | componentId);
		stream.writeShort(key);
		stream.writeShort(items.getSize());
		for(int index = 0; index < items.getSize(); index++) {
			Item item = items.get(index);
			int id = -1;
			int amount = 0;
			if (item != null) {
				id = item.getId();
				amount = item.getAmount();
			}
			stream.writeByteC(amount >= 255 ? 255 : amount);
			if(amount >= 255)
				stream.writeIntV1(amount);
			stream.writeShortLE(id + 1);
		}
		stream.endPacketVarShort();
		session.write(stream);
	}
	
	public void sendLogout() {
		OutputStream stream = new OutputStream();
		stream.writePacket(236);
		ChannelFuture future = session.write(stream);
		if(future != null) 
			future.addListener(ChannelFutureListener.CLOSE);
		else
			session.getChannel().close();
	}
	
	public void sendGameMessage(String text) {
		sendGameMessage(text, false);
	}
	
	public void sendGameMessage(String text, boolean filter) {
		sendMessage(filter ? 22 : 0, text, null);
	}
	
	public void sendAllianceRequestMessage(String text, Player p) {
		sendMessage(21, text, p);
	}
	
	public void sendAssistMessage(String text) {
		sendMessage(13, text, null);
	}
	
	public void sendClanMessage(String text) {
		sendMessage(11, text, null);
	}
	
	public void sendPrivateMessage(String text) {
		sendMessage(5, text, null);
	}
	
	public void sendPainelBoxMessage(String text) {
		sendMessage(99, text, null);
	}
	
	public void sendTradeMessage(String text) {
		sendMessage(12, text, null);
	}
	
	public void sendClanWarsRequestMessage(Player p) {
		sendMessage(16, "", p);
	}

	public void sendDuelChallengeRequestMessage(Player p, boolean friendly) {
		sendMessage(friendly ? 15 : 14, "", p);
	}
	
	public void sendPrivateMessageTo(String text, Player p) {
		sendMessage(6, text, p);
	}
	
	public void sendPrivateMessageFrom(String text, Player p) {
		sendMessage(3, text, p);
	}
	
	public void sendPublicMessage(String text, Player p) {
		sendMessage(1, text, p);
	}
	
	public void sendTradeRequestMessage(String text, Player p) {
		sendMessage(4, text, p);
	}
	
	public void sendChallengeRequestMessage(String text, Player p) {
		sendMessage(8, text, p);
	}
	
	public void sendMessage(int type, String text, Player p) {
		int maskData = 0;
		if(type == 1 || type == 3 || type == 4 || type == 6 || type == 7 || type == 8 || type == 9 || type == 10 || type == 14 || type == 15) {
			maskData |= 0x1;
			if(p.hasDisplayName())
					maskData |= 0x2;
			
		}
		OutputStream stream = new OutputStream();
		stream.writePacketVarByte(193);
		stream.writeSmart(type);
		stream.writeInt(0); //junk, not used by client
		stream.writeByte(maskData);
		if((maskData & 0x1) != 0) {
		stream.writeString(p.getDisplayName());
			if(p.hasDisplayName())
				stream.writeString(Utils.formatPlayerNameForDisplay(p.getUsername()));
		}
		stream.writeString(text);
		stream.endPacketVarByte();
		session.write(stream);
	}

	public void sendSound(int id, int delay) {
		OutputStream stream = new OutputStream(7);
		stream.writePacket(136);
		stream.writeShort(id);
		stream.writeByte(100); //Volume
		stream.writeShort(delay);
		stream.writeByte(255);
		session.write(stream);
	}
	
	public void sendMusic(int id) {
		OutputStream stream = new OutputStream(4);
		stream.writePacket(188);
		stream.writeShortLE128(id);
		stream.write128Byte(255);
		session.write(stream);
	}
	
	
	
	public void sendSkillLevel(int skill) {
		OutputStream stream = new OutputStream(7);
		stream.writePacket(38);
		stream.writeByte128(player.getSkills().getLevel(skill));
		stream.writeIntV1((int) player.getSkills().getXp(skill));
		stream.writeByteC(skill);
		session.write(stream);
	}
	
}
